classdef ffseq < frameseq
    % The class representing a sequence of frames from image files
    %
    
    
    %% Properties
    
    properties(GetAccess='public', SetAccess='private')
        dirpath;    % the source directory path
        filenames;  % the cell array of filenames
        
        do_cache;   % whether to cache images
        ncached;    % the number of cached images
    end
    
    properties(GetAccess='private', SetAccess='private')
        cached;     % the cell array of cached images
    end    
    
    
    %% Constructor
    
    methods
        
        function seq = ffseq(dirpath, fmt, indices, varargin)
            % Construct a frame sequence 
            %
            %   seq = ffseq(dirpath, fmt, indices, ...);
            %
            %       constructs a sequence of frames, given the
            %       specification of filenames
            %
            %       Here, cpat is the sprintf format of the filename
            %       pattern, and indices is a vector of integer numbers
            %       for different frames, used in the filename pattern
            %
            %       One can further specify other properties via
            %       name/value pairs:
            %
            %       - 'size':   The frame size in form of [h w]
            %       - 'pix':    The pixel format, either 'gray' or 'rgb'.
            %       - 'cache':  whether to cache images
            %                   (default = false)
            %
            %       If not specified, these properties will be inferred
            %       from the first frame. 
            %
            
            % parse arguments
            
            if ~ischar(dirpath)
                error('ffseq:invalidarg', 'dirpath should be a string.');
            end
            
            if ~ischar(fmt)
                error('ffseq:invalidarg', 'fmt should be a string.');
            end
            
            if ~(isnumeric(indices) && isvector(indices))
                error('ffseq:invalidarg', 'ffseq should be a numeric vector.');
            end
                        
            % get properties
            
            siz = [];
            pix = [];
            cache = false;
            
            if ~isempty(varargin)
                
                pnames = varargin(1:2:end);
                pvals = varargin(2:2:end);
                
                if ~(iscellstr(pnames) && numel(pnames) == numel(pvals))
                    error('ffseq:invalidarg', ...
                        'The name/value list is invalid.');
                end
                
                for i = 1 : numel(pnames)
                    cn = pnames{i};
                    cv = pvals{i};
                    
                    switch lower(cn)
                        case 'size'
                            if ~(isnumeric(cv) && isvector(cv) && numel(cv) == 2)
                                error('ffseq:invalidarg', ...
                                    'size should be a pair of numbers.');
                            end
                            siz = cv;
                            
                        case 'pix'
                            if ~(ischar(cv) && ismember(cv, {'gray', 'rgb'}))
                                error('ffseq:invalidarg', ...
                                    'pix should be either ''gray'' or ''rgb''.');
                            end
                            pix = cv;
                            
                        case 'cache'
                            if ~(islogical(cv) && isscalar(cv))
                                error('ffseq:invalidarg', ...
                                    'The value of cache should be a logical scalar.');                                
                            end
                            cache = cv;
                            
                        otherwise
                            error('ffseq:invalidarg', ...
                                'Unknown option name %s', cn);
                    end
                    
                end
            end                        
            
            % get filenames
            
            fns = arrayfun(@(i) sprintf(fmt, i), indices, 'UniformOutput', false);
            
            % infer non-given properties
            
            if isempty(siz) || isempty(pix)
                
                F = imread(fullfile(dirpath, fns{1}));
                
                if isempty(siz)
                    siz = [size(F,1), size(F,2)];
                end
                
                if isempty(pix)
                    if size(F,3) == 1
                        pix = 'gray';
                    elseif size(F,3) == 3
                        pix = 'rgb';
                    else
                        error('ffseq:rterror', ...
                            'The image form is unsupported.');
                    end
                end                
            end
            
            % set fields
            
            seq.height = siz(1);
            seq.width = siz(2);
            seq.pixelfmt = pix;
            seq.nframes = numel(fns);
            
            seq.dirpath = dirpath;
            seq.filenames = fns;    
            seq.do_cache = cache;
            seq.ncached = 0;
            if cache
                seq.cached = cell(numel(fns), 1); 
            end
        end

    end
    
    
    %% Methods
    
    methods
        
        function F = frame(seq, i)
            % Get a frame from the sequence
            %
            %   F = seq.frame(i);
            %
            
            h = seq.height;
            w = seq.width;
            pix = seq.pixelfmt;
            
            if seq.do_cache
                F = seq.cached{i};
            else
                F = [];
            end
            
            if isempty(F)
                F = imread(fullfile(seq.dirpath, seq.filenames{i}));
                
                if strcmp(pix, 'gray')
                    if size(F, 3) == 3
                        F = rgb2gray(F);
                    end
                elseif strcmp(pix, 'rgb')
                    if size(F, 3) == 1
                        F = cat(3, F, F, F);
                    end
                end
                
                if ~(size(F, 1) == h && size(F, 2) == w)
                    F = imresize(F, [h w]);
                end 
                
                if seq.do_cache
                    seq.cached{i} = F;
                    seq.ncached = seq.ncached + 1;
                end
            end            
        end
        
        
        function Fs = frames(seq, indices)
            % Get frames and return them as a cell array
            %
            %   Fs = seq.frames();
            %       Gets all frames
            %
            %   Fs = seq.frames(indices);
            %       Gets the specified frames
            %
            
            if nargin < 2
                indices = (1:seq.nframes).';
            end
            
            Fs = cell(size(indices));
            
            for i = 1 : numel(indices)
                Fs{i} = frame(seq, i);
            end                        
        end
        
    
    end
    
end





